package com.yanqu.road.handle.ws;

import com.yanqu.road.define.YanQuWSDefine;
import com.yanqu.road.message.YanQuMessage;
import com.yanqu.road.AgentServerNettyService;
import com.yanqu.road.server.protocol.Protocol;
import com.yanqu.road.task.ClientCommandTask;
import com.yanqu.road.factory.YanQuWSHFactory;
import com.yanqu.road.player.PlayerControl;
import com.yanqu.road.utils.string.StringUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.yanqu.road.thread.ThreadPoolHelper;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

public class WSHandler extends SimpleChannelInboundHandler<Object> {

    private static Logger logger = LogManager.getLogger(WSHandler.class.getName());

    private String remoteIp;

    private WebSocketServerHandshaker handshaker;

    public String getRemoteIp() {
        return remoteIp;
    }

    public WSHandler() {
        super();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof PongWebSocketFrame) {//Pong不用回复
        } else if (msg instanceof PingWebSocketFrame) {
            pingHandler(ctx);
        } else if (msg instanceof FullHttpRequest) {
            httpHandler(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof BinaryWebSocketFrame) {
            webSocketHandler(ctx, (BinaryWebSocketFrame) msg);
        } else if (msg instanceof CloseWebSocketFrame) {
            closeWebSocketHandler(ctx, (CloseWebSocketFrame) msg);
        }
    }

    private void pingHandler(ChannelHandlerContext ctx) {
        byte[] bytes = new byte[2];
        bytes[0] = (byte) 0x8A;
        bytes[1] = (byte) 0x00;
        ByteBuf byteBuf = Unpooled.buffer(2);
        byteBuf.writeBytes(bytes);
        ctx.channel().write(byteBuf);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);

        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                Long playerId = ctx.channel().attr(AgentServerNettyService.KEY_FOR_PLAYER_ID).get();
                if (playerId != null) {
                    PlayerControl.removePlayerOnline(playerId, ctx.channel(), false);
                }
                logger.info("WebSocket is idle, playerId :{} " , playerId);
            }
        }
    }

    protected void httpHandler(ChannelHandlerContext ctx, FullHttpRequest request) {
        if (!HttpMethod.GET.equals(request.getMethod())
                || !YanQuWSDefine.HTTP_WEB_SOCKET.equalsIgnoreCase(request.headers().get(YanQuWSDefine.HTTP_HEADER_UPGRADE))
        ) {
            DefaultHttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
            ctx.channel().write(resp);
            ctx.channel().close();
            return;
        }

        String webSocketURL = "ws://" + request.headers().get(HttpHeaders.Names.HOST);
        YanQuWSHFactory wsShakerFactory = new YanQuWSHFactory(webSocketURL, null, false);

        handshaker = wsShakerFactory.getHandshaker(request);

        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            String ip;
            String secWebsocketProtocol = null;
            try {
                HttpHeaders headers = request.headers();
                ip = headers.get(YanQuWSDefine.HTTP_HEADER_X_REAL_IP);
                if (StringUtils.isNullOrEmpty(ip)) {
                    ip = "";
                }
                secWebsocketProtocol = headers.get(YanQuWSDefine.HTTP_HEADER_SEC_WEB_SOCKET_PROTOCOL);
                if (StringUtils.isNullOrEmpty(secWebsocketProtocol)) {
                    secWebsocketProtocol = "";
                }
                ctx.attr(AgentServerNettyService.KEY_FOR_PLAYER_IP).set(ip);
                ctx.attr(AgentServerNettyService.KEY_FOR_SEC_WEBSOCKET_PROTOCOL).set(secWebsocketProtocol);
                logger.info("WebSocket ip : {}, secWebsocketProtocol : {}", ip, secWebsocketProtocol);
                remoteIp = ip;
            } catch (Exception ex) {

            }
            if (StringUtils.isNullOrEmpty(secWebsocketProtocol)) {
                handshaker.handshake(ctx.channel(), request);
            } else {
                HttpHeaders headers = new DefaultHttpHeaders(true);
                headers.add(YanQuWSDefine.HTTP_HEADER_SEC_WEB_SOCKET_PROTOCOL, secWebsocketProtocol);
                handshaker.handshake(ctx.channel(), request, headers, ctx.channel().newPromise());
            }
        }
    }

    protected void webSocketHandler(ChannelHandlerContext ctx, BinaryWebSocketFrame frame) {
        YanQuMessage packet = new YanQuMessage();
        packet.read(frame.content());
        Long playerId = ctx.channel().attr(AgentServerNettyService.KEY_FOR_PLAYER_ID).get();
        int code = packet.getCode();
        if(code != Protocol.A_LOGIN_REQUEST){
            if(null == playerId){
                return;
            }
            if(playerId != packet.getPlayerId()){
                logger.error("======dirty userId:{}, op userId:{}, code:{}", playerId, packet.getPlayerId(), code);
            }
            packet.setPlayerId(playerId);
        }
        ThreadPoolHelper.exec(new ClientCommandTask(ctx, packet));
    }

    protected void closeWebSocketHandler(ChannelHandlerContext ctx, CloseWebSocketFrame frame) {
        Long playerId = ctx.channel().attr(AgentServerNettyService.KEY_FOR_PLAYER_ID).get();
        if (playerId != null) {
            PlayerControl.delayRemovePlayerOnline(playerId, ctx.channel());
        }

        logger.info("WebSocket close, playerId : {}" , playerId);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);

        Long playerId = ctx.channel().attr(AgentServerNettyService.KEY_FOR_PLAYER_ID).get();
        if (playerId != null) {
            PlayerControl.removePlayerOnline(playerId, ctx.channel(), false);
        }

        logger.warn("WebSocket exception, playerId : " + playerId, cause);
        ctx.close();
    }

}
